import { Observable } from 'rxjs'
import {
  map,
  scan,
  filter,
  takeWhile,
  withLatestFrom,
  switchMap,
  take
} from 'rxjs/operators'
import { KEYCODE, BODY_RADIUS, BODY_RADIUS_ADD } from './constant'
import { ticker$, keyDown$, speed$, direction$ } from './observable'
import { createFoodPosition, detectionEdge, detectionFood, draw } from './utils'

const scoreElem = document.getElementById('score') as HTMLElement
const tipsElem = document.getElementById('tips') as HTMLElement
const canvas = document.getElementById('canvas') as HTMLCanvasElement
const ctx = canvas.getContext('2d') as CanvasRenderingContext2D

const initState: State = {
  score: 0,
  direction: KEYCODE.LEFT,
  user: {
    x: canvas.width / 2,
    y: canvas.height / 2,
    r: BODY_RADIUS,
    color: 'red'
  },
  food: createFoodPosition(ctx)
}

/** 游戏主逻辑 */
const main$ = ticker$.pipe(
  // 以ticker心跳为发送点，发送多个事件流当前值
  withLatestFrom<Time, Observable<number>, Observable<number>>(
    speed$,
    direction$
  ),
  // 计算当前事件流中，视图state的数据
  scan<[Time, number, number], State>((state, [ticker, speed, direction]) => {
    const move = ticker.delay * speed
    let { score, user, food } = state

    switch (direction) {
      case KEYCODE.UP:
        user.y = user.y - move
        break
      case KEYCODE.DOWN:
        user.y = user.y + move
        break
      case KEYCODE.LEFT:
        user.x = user.x - move
        break
      case KEYCODE.RIGHT:
        user.x = user.x + move
        break
    }

    if (detectionFood(state)) {
      food = createFoodPosition(ctx)
      user.r = user.r + BODY_RADIUS_ADD
      score++
    }

    return {
      score,
      direction,
      user,
      food
    }
  }, initState),
  // 碰撞到地图边缘结束游戏
  takeWhile<State>((state) => detectionEdge(ctx, state))
)

/** 入口执行Observable */
const game$ = keyDown$.pipe(
  map<KeyboardEvent, number>((e) => e.keyCode),
  // 只识别Enter键
  filter<number>((keyCode) => [KEYCODE.ENTER].includes(keyCode)),
  // 只获取一次
  take(1),
  switchMap<number, Observable<State>>(() => main$)
)

/** 入口响应Observer：操作view */
game$.subscribe({
  next: (state) => {
    draw(ctx, state)
    scoreElem.innerHTML = `${state.score}`
  },
  error: (err) => console.log('错误', err),
  complete: () => {
    tipsElem.innerHTML = '游戏结束，请刷新浏览器再试'
  }
})
